package core;

import java.util.Iterator;

import core.exception.IllegalAddException;
import core.exception.InvalidConversionException;
import core.exception.ModelException;
import core.reports.IReportVisitor;
import core.reports.TraversingType;

/**This class represents a ProductGroup. It extends Container. A product
 * Group can contain other ProductGroups, Items, and Products. A ProductGroup
 * has additional functionality for storing and calculating three month supplies.
 * 
 * @author Natasha
 *
 */

//Container extends ModelObject, so no need to extend ModelObject here
public class ProductGroup extends Container {
	private static final long serialVersionUID = 974751510759414067L;
	private Amount _threeMonthSupply;

	/**Construct a new ProductGroup. In principle, a container is only useful if 
	 * constructed by a parent through the addContainer(String name) method.
	 * 
	 * @param name The name of the container. Names must be nonempty and unique 
	 * within the parent container.
	 * @throws IllegalArgumentException
	 */
	protected ProductGroup(String name) throws IllegalArgumentException {
		// This method's preconditions are all checked by the super class
		super(name);
		
		// Initialize the threeMonthSupply to 0 which means unspecified.
		// Use the unit of measure count as a default.
		_threeMonthSupply = new Amount(0,UnitOfMeasurement.count);
	}

	/**Return the threeMonthSupply needed for this product group.
	 * 
	 * @return The three month supply needed for this product group.
	 */
	public Amount getThreeMonthSupply() {
		// This method has no preconditions
		return _threeMonthSupply;
	}
	
	/**Test if an amount is a valid amount for a ProductGroup's
	 * three month supply. An amount must be nonnegative. 0 the three
	 * month supply is unspecified. If the unit of the amount is "count",
	 * then the float magnitude be an integer.
	 * 
	 * @param supply The value to set the tree month supply to.
	 * @return True if the supply is valid, false otherwise.
	 */
	static public Boolean canSetThreeMonthSupply(Amount supply){
		// This method has no preconditions
		if(supply == null || supply.getQuantity() < 0)
			return false;
		if(supply.getUnit() == UnitOfMeasurement.count){
			// then the float must be an integer. So the difference between the float
			// and the int must be 0;
			return ((supply.getQuantity() - ((int)supply.getQuantity())) == 0);
		}
		return true;
	}
	
	/**Set the ProductGroup's three month supply to amount. amount must be 
	 * nonnegative, and if the unit is "count" the magnitude must be an 
	 * integer.
	 *  
	 * @param amount The new three month supply.
	 * @throws IllegalArgumentException
	 */
	protected void setThreeMonthSupply(Amount amount) throws IllegalArgumentException{
		// Check preconditions
		if (!canSetThreeMonthSupply(amount))
			throw new IllegalArgumentException("Can't set three month supply"); 
		_threeMonthSupply = amount;
	}
	
	/**Return the additional amount of product needed for this product group
	 * to have a full n month supply. If the three month supply is unspecified
	 * (0), then this method returns 0 (no more supply needed).
	 * 
	 * @return The additional amount of product needed for this ProductGroup
	 * to have a full n month supply.
	 * 
	 * @throws ModelException thrown if months < 1
	 */
	public Amount getAmountNeededForNMonthSupply(int months) throws ModelException
	{
		//TODO: need to fix logic in getAmountNeededForNMonthSupply so that if the unit of measure 
		//		is count, then count the items in the product group.
		// Check Preconditions
		if (!canGetAmountNeededForNMonthSupply(months))
			throw new ModelException("Invalid number of months");
		
		Amount aMonthSupply = _threeMonthSupply.divide(3);
		Amount neededSupply = aMonthSupply.multiply(months);
		getNMonthAmountRecursive(neededSupply,this);
		return neededSupply;
	}
	
	private void getNMonthAmountRecursive(Amount neededSupply, Container child){
		Iterator<Item> itemsIter = child.getItems();
		if(neededSupply.getQuantity() == 0)
			return;
		if (itemsIter != null){
			while (itemsIter.hasNext())	{
				Item item = itemsIter.next();
				Amount itemAmount = item.getAmount();
				try{
					neededSupply.subtract(itemAmount);
				}
				catch(InvalidConversionException e)	{
					//do nothing if invalid
				}
			}
		}
		
		Iterator<Container> children = child.getChildContainers();
		while(children.hasNext()){
			getNMonthAmountRecursive(neededSupply,children.next());
		}
	}

	
	/**
	 * Checks whether can get the needed amount for the months specified
	 * 
	 * @param months the number of month
	 * 
	 * @return returns true if can get the needed supply, else returns false
	 */
	public boolean canGetAmountNeededForNMonthSupply(int months)
	{
		if (months < 1)
			return false;
		return true;
	}
	
	/**Add a ProductGroup to this ProductGroup.
	 * 
	 * @param name The name of the new ProductGroup. The name must be nonempty
	 * and unique within this ProductGroup's direct children.
	 * @throws IllegalAddException
	 * @return The new ProductGroup added.
	 */	
	@Override
	protected Container addContainer(String name) throws IllegalAddException {
		// Check Preconditions
		if (!canAddContainer(name))
			throw new IllegalAddException("Can't add container");
		
		ProductGroup child = new ProductGroup(name);
		
		// add the child to self
		_children.put(name, child);
		
		// update the child's pointers
		child._parent = this;
		child._storageUnit = _storageUnit;
		
		// update the storage unit, so it knows about the new descendant
		_storageUnit.addDescendant(child);
		
		// Return the child to the user so the user can further modify it.
		return child;
	}

	@Override
	public void accept(IReportVisitor v) {
		TraversingType t = v.getType();
		
		if (t == TraversingType.PREORDER) visitProductGroup(v);
		
		for(Container c : _children.values()) {
			c.accept(v);
		}
		
		if (t == TraversingType.POSTORDER) visitProductGroup(v);
		
	}
	
	private void visitProductGroup(IReportVisitor v)
	{	
		Iterator<Item> iIter = getItems();
		while(iIter != null && iIter.hasNext()) {
			iIter.next().accept(v);
		}
		v.visitProductGroup(this);
	}


}
